package courier

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"fmt"
	"gitee.com/dongmingchao/decent-ft/src/caretaker"
	"gitee.com/dongmingchao/decent-ft/src/event"
	resourcePool "gitee.com/dongmingchao/decent-ft/src/resource-pool"
	"gitee.com/dongmingchao/decent-ft/src/utils"
	"log"
	"net"
	"os"
	"os/signal"
	"syscall"
	"time"
)

//import "github.com/thoas/go-funk"

func freePort() *net.UDPAddr {
	raddr, _ := net.ResolveUDPAddr("udp", "8.8.8.8:22")
	conn, err := net.DialUDP("udp", nil, raddr)
	if err != nil {
		log.Fatal(err)
	}
	defer func() {
		_ = conn.Close()
	}()
	laddr, _ := net.ResolveUDPAddr("udp", conn.LocalAddr().String())
	return laddr
}

type Op byte

const (
	Done Op = 1 + iota
	AskIndex
	Get
	Connect
	TCP
	Pass
	Search
	Update
	Bypass
	Remove
	Patch
)

func GetIndexInfo(rAddr *net.UDPAddr) resourcePool.GTree {
	buf := remoteCall(rAddr, AskIndex, nil)
	stash := resourcePool.GTree{}
	stash.Read(bytes.NewReader(buf))
	return stash
}

func GetFileContent(rAddr *utils.UDPAddr, hash [20]byte) *bytes.Buffer {
	buf := remoteCall(rAddr.UDPAddr, Get, func(conn *net.UDPConn) []byte {
		return hash[:]
	})
	return bytes.NewBuffer(buf)
}

func ConnectNeighbor(rAddr *net.UDPAddr) []byte {
	var sent bytes.Buffer
	return remoteCall(rAddr, Connect, func(conn *net.UDPConn) []byte {
		err := conn.SetDeadline(time.Now().Add(time.Second))
		if err != nil {
			fmt.Println("Connect Deadline", err)
		}
		srcAddr, _ := net.ResolveUDPAddr("udp", conn.LocalAddr().String())
		srcAddr.Port = poolAddr.Port
		addr := utils.NewUDPAddr(srcAddr)
		addr.Write(&sent)
		caretaker.GlobalStash.Write(&sent)
		return sent.Bytes()
	})
}

func remoteCall(rAddr *net.UDPAddr, code Op, rest func(*net.UDPConn) []byte) []byte {
	conn := makeConn(rAddr)
	data := []byte{byte(code)}
	if rest != nil {
		data = append(data, rest(conn)...)
	}
	SendData(conn, data)
	buf := recv(conn, code)
	err := conn.Close()
	if err != nil {
		log.Fatalln("remoteCall close error", err)
	}
	return buf
}

//func localCall(rAddr utils.UDPAddr, rest func(*net.UDPConn) []byte) {
//	data := []byte{byte(Pass)}
//	SilentSend(rAddr, func(conn *net.UDPConn) []byte {
//		if rest != nil {
//			data = append(data, rest(conn)...)
//		}
//		return data
//	})
//}

func SendData(conn *net.UDPConn, ask []byte) {
	_, err := conn.Write(ask)
	if err != nil {
		log.Fatalln("Send Data", err)
	}
}

func makeConn(rAddr *net.UDPAddr) *net.UDPConn {
	conn, err := net.DialUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0}, rAddr)
	if err != nil {
		log.Fatalln("makeConn", err)
	}
	return conn
}

//func Send(rAddr *net.UDPAddr, ask []byte) *net.UDPConn {
//	conn := makeConn(rAddr)
//	SendData(conn, ask)
//	return conn
//}

func SilentSend(rAddr utils.UDPAddr, data func(*net.UDPConn) []byte) {
	conn := makeConn(rAddr.UDPAddr)
	SendData(conn, data(conn))
	err := conn.Close()
	if err != nil {
		log.Fatal(err)
	}
}

type Response struct {
	length uint16
	code   Op
	reader *bufio.Reader
	conn   net.Conn
}

func presetRead(conn net.Conn) (Response, error) {
	reader := bufio.NewReader(conn)
	lengthBuf := make([]byte, 2)
	_, err := reader.Read(lengthBuf)
	if err != nil {
		fmt.Println("presetRead read error", err)
		return Response{}, err
	}
	length := binary.BigEndian.Uint16(lengthBuf)
	bakCode, err := reader.ReadByte()
	if err != nil {
		log.Fatal(err)
	}
	return Response{
		length,
		Op(bakCode),
		reader,
		conn,
	}, nil
}

func recv(conn net.Conn, askCode Op) []byte {
	resp, err := presetRead(conn)
	if err != nil {
		return nil
	}
	for _, mw := range feedbackMiddleWares {
		ret := mw(askCode, resp)
		if ret != nil {
			return ret
		}
	}
	println("panic, response not be handled")
	return nil
}

var poolAddr *net.UDPAddr

func Start(wg event.Bus) {
	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
	var conn *net.UDPConn
	var err error
	go func() {
		feedbackMiddleWares = append(feedbackMiddleWares, simpleShow, handleConnect)
		lAddr, err := net.ResolveUDPAddr("udp", ":0")
		if err != nil {
			log.Fatalln("Find Port Error")
		}
		conn, err = net.ListenUDP("udp", lAddr)
		if err != nil {
			fmt.Println("ListenUDP err:", err)
			return
		}
		poolAddr, err = net.ResolveUDPAddr("udp", conn.LocalAddr().String())
		if err != nil {
			log.Fatalln("pool addr resolve error")
		}
		fmt.Println("local addr: ", poolAddr)
		for {
			readBuf := make([]byte, 0xffff+2) // TODO: 判断超出最大
			n, rAddr, err := conn.ReadFromUDP(readBuf)
			if err != nil {
				// connect closed because of ctrl-c
				log.Printf("Because of Shutdown, %v", err)
				break
			}
			reader := bufio.NewReader(bytes.NewReader(readBuf))
			buf := handleListen(reader, rAddr)
			if len(buf) == 0 {
				continue
			}
			n, err = conn.WriteTo(finalResponse(buf), rAddr)
			if err != nil {
				log.Fatal(err)
			}

			println("remote addr: ", rAddr.String())
			println("msg length: ", n)
			//fmt.Println("msg content", buf)
			println("feed back finish: ", rAddr.String())
		}
	}()
	go func() {
		bridgeAddr, err := net.ResolveTCPAddr("tcp", ":0")
		if err != nil {
			log.Fatalln("TCP Find Port Error")
		}
		BridgeListen(bridgeAddr)
	}()
	wg.Emit("courier-ready")
	<-sigs
	err = conn.Close()
	if err != nil {
		log.Fatal(err)
	}
	defer wg.Emit("stop-courier")
}
